#pragma once
#include <Rz/Object.h>
#include <Rz/Memory.h>
#include <Rz/Math.h>
#include <Rz/IHumanReadable.h>
#include "Vector3.h"
#include "Vector4.h"
#include "Matrix3.h"

// References:
// [Matrix::Invert()] (29.11.2011)
// http://stackoverflow.com/questions/1148309/inverting-a-4x4-result
// [Orthographic, Perspective, PerspectiveFieldOfView] (29.11.2011)
// Decompiled XNA Matrix class
// http://nehe.ceske-hry.cz/cl_gl_matice2.php
// highly inspired by http://bartipan.net/vmath/ (24.1.2012)

namespace Rz { namespace Graphics { namespace Math {

// TODO: IComparable?
class Matrix4 :
	public Object,
	public IHumanReadable
{
	public:
		RZ_RTTI(Rz::Graphics, Matrix4);
		RZ_RTTI_IMPLEMENTS(Object, IHumanReadable);
		RZ_RTTI_END;

		union
		{
			struct 
			{
				f32 M11, M21, M31, M41;
				f32 M12, M22, M32, M42;
				f32 M13, M23, M33, M43;
				f32 M14, M24, M34, M44;
			};

			struct  
			{
				f32 Cell[16];
			};
		};

		Matrix4();
		Matrix4(const Matrix4& m);

		Matrix4(f32 m11, f32 m21, f32 m31, f32 m41,
				f32 m12, f32 m22, f32 m32, f32 m42,
				f32 m13, f32 m23, f32 m33, f32 m43,
				f32 m14, f32 m24, f32 m34, f32 m44);

		Matrix4(f32 m[4][4]);
		Matrix4& operator = (const Matrix4& m);
		Matrix4& operator = (f32 m[4][4]);

		bool operator != (const Matrix4& m) const;
		Vector3 operator * (const Vector3& v) const;
		Vector4 operator * (const Vector4& v) const;
		Matrix4 operator * (const Matrix4& m) const;
		Matrix4& operator *= (const Matrix4& m);
		Matrix4 operator / (f32 n) const;
		Matrix4& operator /= (f32 n);

		friend bool operator == (const Matrix4& l, const Matrix4& r);

		void SetIdentity();
		Matrix4& Invert();
		Matrix4 Inverted() const;
		
		Matrix4& Transpose();
		Matrix4 Transposed() const;

		void RotateX(f32 angle);
		void RotateY(f32 angle);
		void RotateZ(f32 angle);

		void Rotate(f32 x, f32 y, f32 z);
		void Rotate(const Vector3& v);

		void Translate(f32 x, f32 y, f32 z);
		void Translate(const Vector3& v);

		void Scale(f32 x, f32 y, f32 z);
		void Scale(const Vector3& v);
		void Scale(f32 scale);

		void LookAt(const Vector3& position, const Vector3& target, const Vector3& up);

		void LookAt(f32 posX, f32 posY, f32 posZ,
					f32 tarX, f32 tarY, f32 tarZ,
					f32 upX, f32 upY, f32 upZ);

		Vector3 GetTranslation() const;

		Vector4 GetRow(u32 row);
		Vector4 GetColumn(u32 column);

		f32 GetCell(u32 index) const;
		f32 GetCell(u32 x, u32 y) const;

		void SetCell(u32 index, f32 value);
		void SetCell(u32 x, u32 y, f32 value);

		Matrix3 ToMatrix3() const;

		// IHumanReadable
		virtual String ToString() const override;

		// static
		static Matrix4 CreateRotationX(f32 angle);
		static Matrix4 CreateRotationY(f32 angle);
		static Matrix4 CreateRotationZ(f32 angle);

		static Matrix4 CreateRotation(f32 x, f32 y, f32 z);
		static Matrix4 CreateRotation(const Vector3& v);

		static Matrix4 CreateTranslation(f32 x, f32 y, f32 z);
		static Matrix4 CreateTranslation(const Vector3& v);

		static Matrix4 CreateScale(f32 x, f32 y, f32 z);
		static Matrix4 CreateScale(const Vector3& v);
		static Matrix4 CreateScale(f32 scale);

		static Matrix4 CreateLookAt(const Vector3& position, const Vector3& target, const Vector3& up);
		static Matrix4 CreateLookAt(f32 posX, f32 posY, f32 posZ,
								   f32 tarX, f32 tarY, f32 tarZ,
								   f32 upX, f32 upY, f32 upZ);

		static Matrix4 CreateInverseLookAt(const Vector3& position, const Vector3& target, const Vector3& up, f32 planeY);
		static Matrix4 CreateInverseLookAt(f32 posX, f32 posY, f32 posZ,
											f32 tarX, f32 tarY, f32 tarZ,
											f32 upX, f32 upY, f32 upZ, f32 planeY);

		static Matrix4 CreateOrthographic(f32 width, f32 height, f32 nearPlane, f32 farPlane);
		static Matrix4 CreateFrustum(f32 left, f32 right, f32 bottom, f32 top, f32 nearVal, f32 farVal);
		static Matrix4 CreatePerspectiveFieldOfView(f32 fieldOfView, f32 aspectRatio, f32 nearPlane, f32 farPlane);

		static const Matrix4 Zero;
		static const Matrix4 One;
		static const Matrix4 Identity;
};

} } }
